﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Web.Configuration;

namespace WsdlAddress
{
    /// <summary>
    /// Helper class that wraps a regular expression with the capability to
    /// scan a stream for matches.
    /// </summary>
    class StreamMatcher
    {
        /// <summary>
        /// The regex to use in searching the stream
        /// </summary>
        private Regex regex = null;

        /// <summary>
        /// Create a new StreamMatcher that will search the given pattern.
        /// </summary>
        /// <param name="pattern">The pattern to compile into a regular expression</param>
        /// <param name="options">Regular expression options</param>
        public StreamMatcher(string pattern, RegexOptions options)
        {
            // initialize the regular expression with the given pattern
            this.regex = new Regex(pattern, options);
        }

        private IEnumerable<string> GetLineMatches(string line)
        {
            // Add all the matches found in the line to the set of results
            foreach (Match match in this.regex.Matches(line))
            {
                yield return match.Value;
            }
        }

        /// <summary>
        /// Scan the given stream for pattern matches.
        /// </summary>
        /// <param name="stream">The stream to scan</param>
        /// <returns>An array containing all matches of the given pattern</returns>
        public IEnumerable<string> GetMatches(Stream stream)
        {
            // Create list to hold the matches
            // Create a reader for the stream
            StreamReader reader = new StreamReader(stream);
            // Read the text line by line and search form matches
            // Avoid reading the entire response in case the response is large
            while (reader.Peek() != -1)
            {
                // Read the next line
                string line = reader.ReadLine();
                foreach (string match in this.GetLineMatches(line))
                {
                    yield return match;
                }
            }
        }
    }
    /// <summary>
    /// Default service implementation.
    /// </summary>
    public class WsdlAddressService : IWsdlAddressService
    {
        /// <summary>
        /// Fetch the URL and return a readable stream containing the results.
        /// </summary>
        /// <param name="url">The URL to fetch</param>
        /// <returns>A readable stream containing the fetched response</returns>
        private static Stream GetURLAsStream(string url)
        {
            // Create a request to fetch the URL
            WebRequest request = WebRequest.Create(url);
            // Execute the request
            WebResponse response = request.GetResponse();
            // Return the data stream for the response
            return response.GetResponseStream();
        }

        /// <summary>
        /// Return true iff the given URL is an absolute URL.
        /// </summary>
        /// <param name="url">the url to check</param>
        /// <returns>True iss the given URL is an absolulte URL</returns>
        private static bool IsAbsoluteURL(string url)
        {
            // URL is absolute if it starts with a protocol string
            return url.StartsWith("http://") || url.StartsWith("https://");
        }

        /// <summary>
        /// Resolve the given URL against the given base URI.
        /// </summary>
        /// <param name="url">the URL to resolve</param>
        /// <param name="baseUri">the base URI against which to resolve</param>
        /// <returns>The absolute URL string</returns>
        private string ResolvePath(string url, Uri baseUri)
        {
            // Check if it is absolute
            if (IsAbsoluteURL(url))
            {
                // Return the absolute URL
                return url;
            }
            // Normalize relative URI to absolute
            return new Uri(baseUri, url).AbsoluteUri;
        }

        /// <summary>
        /// Transform the array of URLs to an absolute form.
        /// </summary>
        /// <param name="urls">the URLs to resolve</param>
        /// <param name="baseURL">the base URL against which to resolve</param>
        /// <returns>The resolved URLs</returns>
        private string[] ResolvePaths(string[] urls, string baseURL)
        {
            // Create base URI to use in normalizing other urls
            Uri baseUri = new Uri(baseURL);
            // Resolve each URL
            for (int i = 0; i < urls.Length; i++)
            {
                urls[i] = this.ResolvePath(urls[i], baseUri);
            }
            // Return the resolved URLs
            return urls;
        }

        private static string WSDL_REGEX_TEXT_KEY = "wsdl.regex.text";

        /// <summary>
        /// Load the configured value for the regex text to use in matching 
        /// WSDL URLs.
        /// </summary>
        /// <returns>the regex text</returns>
        private static string GetWSDLRegexPatternText()
        {
            return WebConfigurationManager.AppSettings[WSDL_REGEX_TEXT_KEY];
        }

        /// <inheritdoc />
        public string[] GetWsdlAddress(string url)
        {
            // Create a stream containing the results of fetching the URL
            Stream stream = GetURLAsStream(url);
            // Pattern text to match WSDL links
            string wsdlLinkPatternText = GetWSDLRegexPatternText();
            // The matcher to use in scanning for matches
            StreamMatcher streamMatcher =
                new StreamMatcher(wsdlLinkPatternText, RegexOptions.IgnoreCase);
            // Find WSDL URL patterns contained in the stream
            // and create set of unique WSDL URLs that are found
            HashSet<string> wsdlUrls = new HashSet<string>(streamMatcher.GetMatches(stream));
            // Convert the set to an array
            string[] results = wsdlUrls.ToArray();
            // Convert any relative paths to absolute paths based at the input url
            return this.ResolvePaths(results, url);
        }
    }
}
